package io.simple.filters;

import com.google.common.base.Splitter;
import injections.annotations.Property;
import io.simple.core.RequestContext;
import io.simple.core.chain.Filter;
import io.simple.sessions.Session;
import io.simple.sessions.Sessions;
import io.simple.signals.Signal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Check and create (if not exists) session ID, stores it to RequestCotnext under Sessions.SESSION_PROP_NAME key
 * <p/>
 * Created by rehak on 2/25/14.
 */
public class SessionFilter implements Filter {

    private static final Logger LOGGER = LoggerFactory.getLogger(SessionFilter.class);
    private static final String SESSION_COOKIE_NAME = "session";
    private final ReentrantLock lock = new ReentrantLock();
    @Property
    private Sessions sessions;

    @Override
    public ByteBuffer filter(RequestContext context, ByteBuffer data) throws Signal {
        lock.lock();
        try {
            // get cookies
            List<String> cookiesHeaders = context.getRequestHeaders().getValues("Cookie");
            Set<String> sessionIDs = new HashSet<>();
            for (String cookie : cookiesHeaders) {
                List<String> vals = Splitter.on(';').trimResults().omitEmptyStrings().splitToList(cookie);
                for (String sv : vals) {
                    List<String> parts = Splitter.on('=').trimResults().omitEmptyStrings().splitToList(sv);
                    if (parts.size() == 2 && parts.get(0).equals(SESSION_COOKIE_NAME)) {
                        sessionIDs.add(parts.get(1));
                    }
                }
            }
            // browse collected session IDs, look for session
            Set<Session> validSessions = new HashSet<>();
            Set<String> validCookies = new HashSet<>();
            for (String sessionId : sessionIDs) {
                Session s = sessions.get(sessionId);
                if (s != null) {
                    if (s.isValid()) {
                        validSessions.add(s);
                        validCookies.add(s.getID());
                    }
                }
            }

            Set<String> invalidCookies = new HashSet<>(sessionIDs);
            for (Session s : validSessions) {
                invalidCookies.remove(s.getID());
            }
            LOGGER.debug("Session cookies " + sessionIDs + ", valid " + validCookies + ", invalid " + invalidCookies);
            // all ok ?
            Session session;
            if (validSessions.size() == 1) {
                // just 1 valid session
                session = validSessions.iterator().next();
            } else {
                if (validSessions.size() > 1) {
                    LOGGER.warn("Multiple valid sessions!");
                } else {
                    LOGGER.warn("No valid session!");
                }
                session = null;
            }
            if (session == null) {
                if (invalidCookies.size() == 1) {
                    session = sessions.recreate(invalidCookies.iterator().next());
                    invalidCookies.clear();
                    LOGGER.debug("Recreating session " + session);
                } else {
                    session = sessions.create();
                    LOGGER.debug("Creating new session " + session);
                    // set cookie for response header
                }
                context.getResponseHeaders().set("Set-Cookie", SESSION_COOKIE_NAME + "=" + session.getID());
            } else {
                sessions.refresh(session);
                LOGGER.debug("Using session " + session);
            }
            for (String invalid : invalidCookies) {
                context.getResponseHeaders().add("Set-Cookie", SESSION_COOKIE_NAME + "=" + invalid + "; Expires=Thu, 01 Jan 1970 00:00:00 GMT");
            }
            context.setProperty(Sessions.SESSION_PROP_NAME, session.getID());
            return data;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void close() throws Exception {

    }
}
